// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO
// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (c) Microsoft Corporation. All rights reserved

#include "Private.h"
#include "Globals.h"
#include "BaseWindow.h"
#include "ShadowWindow.h"

#define SHADOW_ALPHANUMBER (5)
#define ALPHA(x) (255 - (x))

//// Gray scale values for the shadow grades
#define GS01 255
#define GS02 254
#define GS03 253
#define GS04 252
#define GS05 250
#define GS06 246
#define GS07 245
#define GS08 242
#define GS09 241
#define GS10 227
#define GS11 217
#define GS12 213
#define GS13 212
#define GS14 199
#define GS15 180
#define GS16 172
#define GS17 171
#define GS18 155
#define GS19 144
#define GS20 142

// pre-computed alpha values for the shadow
const BYTE AlphaTable[SHADOW_ALPHANUMBER] = {
	ALPHA(GS04), ALPHA(GS09), ALPHA(GS13), ALPHA(GS17), ALPHA(GS20)
};

const BYTE CornerShadowAlphaMetric[SHADOW_ALPHANUMBER][SHADOW_ALPHANUMBER] = {
	ALPHA(GS08), ALPHA(GS06), ALPHA(GS05), ALPHA(GS03), ALPHA(GS02),
	ALPHA(GS11), ALPHA(GS10), ALPHA(GS09), ALPHA(GS05), ALPHA(GS02),
	ALPHA(GS15), ALPHA(GS14), ALPHA(GS10), ALPHA(GS07), ALPHA(GS03),
	ALPHA(GS18), ALPHA(GS15), ALPHA(GS11), ALPHA(GS08), ALPHA(GS03),
	ALPHA(GS19), ALPHA(GS16), ALPHA(GS12), ALPHA(GS09), ALPHA(GS04),
};


//+---------------------------------------------------------------------------
//
// _Create
//
//----------------------------------------------------------------------------

BOOL CShadowWindow::_Create(ATOM atom, DWORD dwExStyle, DWORD dwStyle, _In_opt_ CBaseWindow* pParent, int wndWidth, int wndHeight) {
	if (!CBaseWindow::_Create(atom, dwExStyle, dwStyle, pParent, wndWidth, wndHeight)) {
		return FALSE;
	}

	return _Initialize();
}

//+---------------------------------------------------------------------------
//
// _WindowProcCallback
//
// Shadow window proc.
//----------------------------------------------------------------------------

LRESULT CALLBACK CShadowWindow::_WindowProcCallback(_In_ HWND wndHandle, UINT uMsg, WPARAM wParam, LPARAM lParam) {
	switch (uMsg) {
	case WM_PAINT:
	{
		HDC dcHandle;
		PAINTSTRUCT ps;

		dcHandle = BeginPaint(wndHandle, &ps);

		HBRUSH hBrush = CreateSolidBrush(_color);
		FillRect(dcHandle, &ps.rcPaint, hBrush);
		DeleteObject(hBrush);

		EndPaint(wndHandle, &ps);
	}
	return 0;

	case WM_SETTINGCHANGE:
		_OnSettingChange();
		break;
	}

	return DefWindowProc(wndHandle, uMsg, wParam, lParam);
}

//+---------------------------------------------------------------------------
//
// _OnSettingChange
//
//----------------------------------------------------------------------------

void CShadowWindow::_OnSettingChange() {
	_InitSettings();

	DWORD dwWndStyleEx = GetWindowLong(_GetWnd(), GWL_EXSTYLE);

	if (_isGradient) {
		SetWindowLong(_GetWnd(), GWL_EXSTYLE, (dwWndStyleEx | WS_EX_LAYERED));
	} else {
		SetWindowLong(_GetWnd(), GWL_EXSTYLE, (dwWndStyleEx & ~WS_EX_LAYERED));
	}

	_AdjustWindowPos();
	_InitShadow();
}

//+---------------------------------------------------------------------------
//
// _OnOwnerWndMoved
//
//----------------------------------------------------------------------------

void CShadowWindow::_OnOwnerWndMoved(BOOL isResized) {
	if (IsWindow(_GetWnd()) && _IsWindowVisible()) {
		_AdjustWindowPos();
		if (isResized) {
			_InitShadow();
		}
	}
}


//+---------------------------------------------------------------------------
//
// _Show
//
//----------------------------------------------------------------------------

void CShadowWindow::_Show(BOOL isShowWnd) {
	_OnOwnerWndMoved(TRUE);
	CBaseWindow::_Show(isShowWnd);
}

//+---------------------------------------------------------------------------
//
// _Initialize
//
//----------------------------------------------------------------------------

BOOL CShadowWindow::_Initialize() {
	_InitSettings();

	return TRUE;
}

//+---------------------------------------------------------------------------
//
// _InitSettings
//
//----------------------------------------------------------------------------

void CShadowWindow::_InitSettings() {
	HDC dcHandle = GetDC(nullptr);

	// device caps
	int cBitsPixelScreen = GetDeviceCaps(dcHandle, BITSPIXEL);

	_isGradient = cBitsPixelScreen > 8;

	ReleaseDC(nullptr, dcHandle);

	if (_isGradient) {
		_color = RGB(0, 0, 0);
		_sizeShift.cx = SHADOW_ALPHANUMBER;
		_sizeShift.cy = SHADOW_ALPHANUMBER;
	} else {
		_color = RGB(128, 128, 128);
		_sizeShift.cx = 2;
		_sizeShift.cy = 2;
	}
}

//+---------------------------------------------------------------------------
//
// _AdjustWindowPos
//
//----------------------------------------------------------------------------

void CShadowWindow::_AdjustWindowPos() {
	if (!IsWindow(_GetWnd())) {
		return;
	}

	HWND hWndOwner = _pWndOwner->_GetWnd();
	RECT rc = { 0, 0, 0, 0 };

	GetWindowRect(hWndOwner, &rc);
	SetWindowPos(_GetWnd(), hWndOwner,
		rc.left + _sizeShift.cx,
		rc.top + _sizeShift.cy,
		rc.right - rc.left,
		rc.bottom - rc.top,
		SWP_NOOWNERZORDER | SWP_NOACTIVATE);
}

//+---------------------------------------------------------------------------
//
// _InitShadow
//
//----------------------------------------------------------------------------

#define GETRGBALPHA(_x_, _y_) ((RGBALPHA *)pDIBits + (_y_) * size.cx + (_x_))

void CShadowWindow::_InitShadow() {
	typedef struct _RGBAPLHA {
		BYTE rgbBlue;
		BYTE rgbGreen;
		BYTE rgbRed;
		BYTE rgbAlpha;
	} RGBALPHA;

	HDC dcScreenHandle = nullptr;
	HDC dcLayeredHandle = nullptr;
	RECT rcWindow = { 0, 0, 0, 0 };
	SIZE size = { 0, 0 };
	BITMAPINFO bitmapInfo;
	HBITMAP bitmapMemHandle = nullptr;
	HBITMAP bitmapOldHandle = nullptr;
	void* pDIBits = nullptr;
	int i = 0;
	int j = 0;
	POINT ptSrc = { 0, 0 };
	POINT ptDst = { 0, 0 };
	BLENDFUNCTION Blend;

	if (!_isGradient) {
		return;
	}

	SetWindowLong(_GetWnd(), GWL_EXSTYLE, (GetWindowLong(_GetWnd(), GWL_EXSTYLE) | WS_EX_LAYERED));

	_GetWindowRect(&rcWindow);
	size.cx = rcWindow.right - rcWindow.left;
	size.cy = rcWindow.bottom - rcWindow.top;

	dcScreenHandle = GetDC(nullptr);
	if (dcScreenHandle == nullptr) {
		return;
	}

	dcLayeredHandle = CreateCompatibleDC(dcScreenHandle);
	if (dcLayeredHandle == nullptr) {
		ReleaseDC(nullptr, dcScreenHandle);
		return;
	}

	// create bitmap
	bitmapInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	bitmapInfo.bmiHeader.biWidth = size.cx;
	bitmapInfo.bmiHeader.biHeight = size.cy;
	bitmapInfo.bmiHeader.biPlanes = 1;
	bitmapInfo.bmiHeader.biBitCount = 32;
	bitmapInfo.bmiHeader.biCompression = BI_RGB;
	bitmapInfo.bmiHeader.biSizeImage = 0;
	bitmapInfo.bmiHeader.biXPelsPerMeter = 100;
	bitmapInfo.bmiHeader.biYPelsPerMeter = 100;
	bitmapInfo.bmiHeader.biClrUsed = 0;
	bitmapInfo.bmiHeader.biClrImportant = 0;

	bitmapMemHandle = CreateDIBSection(dcScreenHandle, &bitmapInfo, DIB_RGB_COLORS, &pDIBits, nullptr, 0);
	if (pDIBits == nullptr || bitmapMemHandle == nullptr) {
		ReleaseDC(nullptr, dcScreenHandle);
		DeleteDC(dcLayeredHandle);
		return;
	}

	memset(pDIBits, 0, ((((32 * size.cx) + 31) & ~31) / 8) * size.cy);

	// edges
	for (i = 0; i < SHADOW_ALPHANUMBER; i++) {
		RGBALPHA* ppxl;
		BYTE bAlpha = AlphaTable[i];

		// bottom
		if (i <= (size.cy + 1) / 2) {
			for (j = SHADOW_ALPHANUMBER; j < size.cx - SHADOW_ALPHANUMBER; j++) {
				ppxl = GETRGBALPHA(j, i);
				ppxl->rgbAlpha = bAlpha;
			}
		}

		// right
		if (i <= (size.cx + 1) / 2) {
			for (j = SHADOW_ALPHANUMBER; j < size.cy - SHADOW_ALPHANUMBER; j++) {
				ppxl = GETRGBALPHA(size.cx - 1 - i, j);
				ppxl->rgbAlpha = bAlpha;
			}
		}
	}

	// corners
	for (i = 0; i < SHADOW_ALPHANUMBER; i++) {
		for (j = 0; j < SHADOW_ALPHANUMBER; j++) {
			RGBALPHA* ppxl;
			BYTE bAlpha;
			bAlpha = CornerShadowAlphaMetric[i][SHADOW_ALPHANUMBER - j - 1];

			// top-right
			if ((i <= (size.cy + 1) / 2) && (j <= (size.cx + 1) / 2)) {
				ppxl = GETRGBALPHA(size.cx - 1 - j, size.cy - 1 - i);
				ppxl->rgbAlpha = bAlpha;
			}

			// bottom-left
			if ((i <= (size.cy + 1) / 2) && (j <= (size.cx + 1) / 2)) {
				ppxl = GETRGBALPHA(j, i + 1);
				ppxl->rgbAlpha = bAlpha;
			}

			// bottom-right
			if ((i <= (size.cy + 1) / 2) && (j <= (size.cx + 1) / 2)) {
				ppxl = GETRGBALPHA(size.cx - 1 - j, i + 1);
				ppxl->rgbAlpha = bAlpha;
			}
		}
	}

	ptSrc.x = 0;
	ptSrc.y = 0;
	ptDst.x = rcWindow.left;
	ptDst.y = rcWindow.top;
	Blend.BlendOp = AC_SRC_OVER;
	Blend.BlendFlags = 0;
	Blend.SourceConstantAlpha = 255;
	Blend.AlphaFormat = AC_SRC_ALPHA;

	bitmapOldHandle = (HBITMAP)SelectObject(dcLayeredHandle, bitmapMemHandle);

	UpdateLayeredWindow(_GetWnd(), dcScreenHandle, nullptr, &size, dcLayeredHandle, &ptSrc, 0, &Blend, ULW_ALPHA);

	SelectObject(dcLayeredHandle, bitmapOldHandle);

	// done
	ReleaseDC(nullptr, dcScreenHandle);
	DeleteDC(dcLayeredHandle);
	DeleteObject(bitmapMemHandle);
}
